/* eslint-disable n/no-process-exit */
import assert from "node:assert/strict";
import fs from "node:fs/promises";
import {
  type ParseArgsConfig,
  type ParseArgsOptionsConfig,
  parseArgs,
} from "node:util";
import chokidar, { type ChokidarOptions } from "chokidar";
import { z } from "zod";
import { showHelp } from "./cli-help.js";
import { computeDiffLib, displayDiffLib } from "./diff.js";

interface MainOptions {
  input: string;
  output: string;
  verbose?: boolean;
  once?: boolean;
}

/**
 * 同步文件内容
 * @param inputPath 输入文件路径
 * @param outputPath 输出文件路径
 * @param verbose 是否显示详细信息
 */
async function syncFileContent(
  inputPath: string,
  outputPath: string,
  verbose = false,
): Promise<void> {
  try {
    const newContent = await fs.readFile(inputPath, "utf8");

    // Read old content if it exists for diff
    let oldContent = "";
    try {
      oldContent = await fs.readFile(outputPath, "utf8");
    } catch {
      // Output file doesn't exist yet, that's fine
    }

    await fs.writeFile(outputPath, newContent, "utf8");
    console.log(`✅ Synced: ${inputPath} → ${outputPath}`);

    // Show diff if verbose mode is enabled and content changed
    if (verbose && oldContent !== newContent) {
      const diff = computeDiffLib(oldContent, newContent);
      displayDiffLib(diff);
    }
  } catch (error) {
    console.error(`❌ Error syncing file: ${error}`);
  }
}
/**
 * 检查命令行参数是否有效
 * 要求:
 * - 输入文件路径和输出文件路径必须是字符串,且不能为空字符串
 * - 输入文件路径和输出文件路径不能相同
 * @param option 命令行参数
 * @returns 参数是否有效
 */
function checkArgs<T extends string | undefined>(option: {
  input: T;
  output: T;
}): option is { input: NonNullable<T>; output: NonNullable<T> } {
  if (typeof option.input !== "string" || typeof option.output !== "string") {
    console.error(
      "❌ Both --input/-i and --output/-o are required when using option flags",
    );
    return false;
  } else if (option.input.length === 0 || option.output.length === 0) {
    console.error(
      "❌ Both --input/-i and --output/-o must be non-empty strings",
    );
    return false;
  } else if (option.input === option.output) {
    console.error("❌ --input/-i and --output/-o must be different files");
    return false;
  }
  return true;
}

/**
 * 解析命令行参数
 * @returns 解析的参数
 */
export async function parseArguments(): Promise<MainOptions | null> {
  if (process.argv.length < 2) {
    return null;
  }
  const args = process.argv.slice(2);

  // If no arguments provided, show help
  if (args.length === 0) {
    return null;
  }

  // Check if first argument is --help
  if (args[0] === "--help" || args[0] === "-h") {
    return null;
  }

  // Check for option flags style: --input/-i and --output/-o (junction)
  const inputAndOutputArgParse = {
    input: {
      type: "string",
      short: "i",
    },
    output: {
      type: "string",
      short: "o",
    },
  } as const;
  const commonArgParse = {
    /**
     * 显示输入文件的变化内容
     */
    verbose: {
      type: "boolean",
      short: "v",
      default: false,
    },
    /**
     * 首次同步时, 同步一次输入文件的文本到输出文件
     */
    "sync-first": {
      type: "boolean",
      default: false,
    },
    once: {
      type: "boolean",
      default: false,
    },
  } as const;

  const mainOption: {
    input: string | undefined;
    output: string | undefined;
    verbose: boolean | undefined;
  } = { input: undefined, output: undefined, verbose: undefined };
  // 优先解析 --input/-i 和 --output/-o 选项
  if (
    // 这里不用严格的匹配 --input/-i 和 --output/-o
    // 而用 --input/-i 或 --output/-o
    // 是因为 可以复用 parseArgs 的报错
    args.includes("--input") ||
    args.includes("-i") ||
    args.includes("--output") ||
    args.includes("-o")
  ) {
    let parsed;
    const parseOption = {
      ...inputAndOutputArgParse,
      ...commonArgParse,
    } satisfies ParseArgsOptionsConfig;
    try {
      parsed = parseArgs({
        args,
        options: parseOption,
      });
    } catch (error) {
      console.error("参数不够", error);
      return null;
    }
    // from parser
    assert.ok(
      parsed?.values,
      "[Internal Error] 参数解析错误, parsed.values 不存在",
    );
    mainOption.input = parsed!.values.input;
    mainOption.output = parsed!.values.output;
    mainOption.verbose = parsed!.values.verbose;
  } else {
    const [_input, _output, ...optionArgs] = args;
    const parsed = parseArgs<ParseArgsConfig>({
      args: optionArgs,
      options: commonArgParse,
    });

    assert.ok(
      parsed?.values,
      "[Internal Error] 参数解析错误, parsed.values 不存在",
    );

    const { verbose: _verbose }: { verbose?: boolean } = parsed.values;
    mainOption.input = _input;
    mainOption.output = _output;
    mainOption.verbose = _verbose;
  }
  // 聚合, 为了下面的类型推导
  const checkedArgs = checkArgs(mainOption);
  if (!checkedArgs) {
    return null;
  }
  // 判断文件系统是否有对应的文件
  const isFileExist = await checkFileExist(mainOption);
  if (!isFileExist) {
    return null;
  }
  return {
    input: mainOption.input,
    output: mainOption.output,
    verbose: mainOption.verbose,
  };
}

/**
 * @desc 检查文件系统是否有对应的文件
 * @param mainOption 包含 input 和 output 路径的对象
 * @returns 文件是否存在
 */
async function checkFileExist<
  T extends {
    input: string;
    output: string;
  },
>({ input, output }: T): Promise<boolean> {
  try {
    await fs.access(input, fs.constants.R_OK | fs.constants.W_OK);
  } catch {
    console.error(`❌ input file ${input} was not accessible!`);
    return false;
  }
  try {
    await fs.access(output, fs.constants.W_OK);
  } catch {
    console.error(`❌ output file ${output} was not accessible!`);
    return false;
  }
  return true;
}

/**
 * @desc 默认的文件系统监听器创建配置函数
 * @param input 输入文件路径
 * @param options 选项参数
 * @returns 一个 chokidar 实例
 */
function createFSWatcher(input: string, options?: ChokidarOptions) {
  return chokidar.watch(input, {
    persistent: true,
    ignoreInitial: true,
    ...options,
  });
}

export async function main(
  options: MainOptions | null = null,
  rtOption: {
    createFSWatcher: typeof createFSWatcher;
  } = {
    createFSWatcher,
  },
): Promise<void> {
  if (!options) {
    showHelp();
    return;
  }

  const { input, output, verbose = false, once = false } = options;

  // Validate that input file exists
  try {
    await fs.access(input);
  } catch {
    console.error(`❌ Input file does not exist: ${input}`);
    return;
  }

  console.log(`🔍 Watching: ${input}`);
  console.log(`📝 Syncing to: ${output}`);
  if (verbose) {
    console.log("🔍 Verbose mode: ON - showing detailed diffs");
  }
  console.log("Press Ctrl+C to stop watching...\n");

  // Initial sync
  await syncFileContent(input, output, verbose);

  // Watch for changes
  const watcher = rtOption.createFSWatcher(input);

  watcher
    .on("change", async (filePath) => {
      console.log(`📄 File changed: ${filePath}`);
      await syncFileContent(input, output, verbose);
      if (once) {
        process.exit(0);
      }
    })
    .on("error", (error) => {
      if (error instanceof Error) {
        console.error(`❌ Watcher error: ${error}`);
        return;
      }
      console.error(`❌ Unknown Watcher error: ${error}`);
    });

  // Handle graceful shutdown
  process.on("SIGINT", () => {
    console.log("\n👋 Stopping file watcher...");
    watcher.close();
    process.exit(0);
  });
}

if (import.meta.main) {
  // 有问必答: 无参数也会打印帮助信息
  const options = await parseArguments();

  main(options).catch(console.error);
}
